home *** CD-ROM | disk | FTP | other *** search
- /*
- File: LogEngine.c
-
- Contains: The core code to talk to the STREAMS log module.
-
- Written by: Quinn "The Eskimo!"
-
- Copyright: © 1998 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- You may incorporate this sample code into your applications without
- restriction, though the sample code has been provided "AS IS" and the
- responsibility for its operation is 100% yours. However, what you are
- not permitted to do is to redistribute the source as "DSC Sample Code"
- after having made changes. If you're going to re-distribute the source,
- we require that you make it clear in the source that the code was
- descended from Apple Sample Code, but that you've made changes.
- */
-
- #define qDebug 1
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the standard C stuff.
-
- #import <stdio.h>
- #import <string.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up low-level OT APIs.
-
- #import <OTDebug.h>
- #import <stropts.h>
- #import <mistream.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the symbolic name of the various OT modules.
-
- #import <modnames.h>
-
- /////////////////////////////////////////////////////////////////////
- // OTDebugStr is not defined in any OT header files, but it is
- // exported by the libraries, so we define the prototype here.
-
- extern pascal void OTDebugStr(const char* str);
-
- /////////////////////////////////////////////////////////////////////
- // Pick up our types and prototypes.
-
- #import "LogEngine.h"
-
- /////////////////////////////////////////////////////////////////////
-
- // If we can't log an entry (because of lack of memory), we
- // increment gDroppedLogEntries. The client can access this to
- // tell the user that we're dropped logs.
-
- static UInt32 gDroppedLogEntries = 0;
-
- // gNewLogEntries is a FIFO containing log entries that have
- // been read but not yet delivered to the client. It is populated
- // at interrupt time by our notifier (LogNotifier) and consumed
- // at system task time by ForEachNewLogEntry.
-
- static OTLIFO gNewLogEntries = { nil };
-
- static SInt32 gNumberInList = 0;
-
- // We record the PSN of the current process when we start up
- // so that we can wake ourselves up when log entries arrive.
- // We also have a flag to say whether we've already called
- // WakeUpProcess, so we don't call it zillions of times.
-
- static ProcessSerialNumber gOurProcess;
- static OTLock gProcessWoken;
-
- /////////////////////////////////////////////////////////////////////
-
- static void CreateNewLogEntry(struct log_ctl *logHeader, OTBuffer *logTextBuffer)
- // Create a new LogEntry, fill out the field fields based on
- // logHeader and the variable length data based on the text in
- // logTextBuffer, and add it to the gNewEntries list of recent
- // additions.
- //
- // Context: OT deferred task
- {
- OSStatus junk;
- OTBuffer *thisTextBuffer;
- UInt32 textLength;
- LogEntryPtr newEntry;
- UInt8 *destCursor;
-
- // First calculate the number of bytes of text that are in
- // this entry (ie in the logTextBuffer and any buffers chained
- // off it).
-
- textLength = 0;
- thisTextBuffer = logTextBuffer;
- while ( thisTextBuffer != nil ) {
- OTAssert("CreateNewLogEntry: Non-data message in buffer chain", thisTextBuffer->fType == M_DATA);
- textLength += thisTextBuffer->fLen;
- thisTextBuffer = thisTextBuffer->fNext;
- }
-
- // Then allocate a new log entry of the correct size,
- // ie the length of the fixed part + the length of the text.
-
- newEntry = (LogEntryPtr) OTAllocMem( sizeof(LogEntry) + textLength );
- if (newEntry == nil) {
- if (gDroppedLogEntries == 0) {
- // OTDebugStr("CreateNewLogEntry: Started dropping log entries");
- }
- OTAtomicAdd32(1, (SInt32 *) &gDroppedLogEntries);
- } else {
-
- // Fill out the fixed fields at the beginning of the
- // new log entry.
-
- newEntry->fNext = nil;
- newEntry->fMagic = kMagicValue;
- newEntry->fRefCount = 1;
- newEntry->fTextLength = textLength;
- newEntry->fLogHeader = *logHeader;
-
- // Now copy bytes out of the text messages into the
- // variable part of the log entry. destCursor is the
- // byte we're going to blat next, ie it starts just
- // after the fixed fields of the new log entry and marches
- // onwards through the variable text data.
-
- destCursor = ((UInt8 *) newEntry) + sizeof(LogEntry);
-
- thisTextBuffer = logTextBuffer;
- while ( thisTextBuffer != nil ) {
- BlockMoveData(thisTextBuffer->fData, destCursor, thisTextBuffer->fLen);
- destCursor += thisTextBuffer->fLen;
- thisTextBuffer = thisTextBuffer->fNext;
- }
- OTAssert("CreateNewLogEntry: Did not copy the number of bytes that we calculated were in the message",
- destCursor = ((UInt8 *) newEntry) + sizeof(LogEntry) + textLength);
-
- // Finally add the new entry to the global list
- // of new entries to be processed at SystemTask time.
-
- OTLIFOEnqueue(&gNewLogEntries, (OTLink *) &newEntry->fNext);
-
- OTAtomicAdd32(1, &gNumberInList);
-
- // If we haven't already woken up our process, do so now.
-
- if ( OTAcquireLock(&gProcessWoken) ) {
- // OTDebugStr("CreateNewLogEntry: Waking up process");
-
- junk = WakeUpProcess(&gOurProcess);
- OTAssert("CreateNewLogEntry: Error waking up our process", junk == noErr);
- }
- }
- }
-
- extern pascal void RetainLogEntry(LogEntryPtr thisEntry)
- // See comment in interface part.
- {
- OTAssert("RetainLogEntry: Bad magic", thisEntry->fMagic == kMagicValue);
- thisEntry->fRefCount += 1;
- }
-
- extern pascal void ReleaseLogEntry(LogEntryPtr thisEntry)
- // See comment in interface part.
- {
- OTAssert("ReleaseLogEntry: Bad magic", thisEntry->fMagic == kMagicValue);
- thisEntry->fRefCount -= 1;
- if ( thisEntry->fRefCount == 0 ) {
- thisEntry->fMagic = 'bad!';
- OTFreeMem(thisEntry);
- }
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static pascal void LogNotifier(StreamRef strm, OTEventCode code, OTResult err, void *cookie)
- // This is the notifier that OT calls when an event happens on
- // the raw stream. In this case the only event we're interested in
- // is SIGPOLL, which says that something interesting has happened with
- // data, typically that data has arrived. We respond to a SIGPOLL
- // by calling OTReadMessage to extract the message from the stream.
- // We then process the message and loop looking for more.
- //
- // OTReadMessage is like getmsg() but it a) operates immediately,
- // so we don't need to wait for a completion event like we do with
- // getmsg, and b) returns the data without copying it.
- //
- // Context: OT deferred task
- {
- #pragma unused(err)
- #pragma unused(cookie)
- OTReadInfo readInfo;
- OTBuffer *readBuffer;
-
- // Debugger();
-
- switch (code) {
- case kSIGNALEVENT + SIGPOLL:
- do {
- readInfo.fType = kOTAnyMsgType;
- readInfo.fCommand = 0; // Not strictly necessary because kOTAnyMsgType implies this field is ignored.
- readBuffer = OTReadMessage(strm, &readInfo);
- if ( readBuffer != nil ) {
- if ( (readBuffer->fType == M_PROTO || readBuffer->fType == M_PCPROTO) &&
- readBuffer->fLen == sizeof(struct log_ctl) ) {
- CreateNewLogEntry( (struct log_ctl *) readBuffer->fData , readBuffer->fNext);
- } else {
- OTDebugStr("LogNotifier: OTReadMessage returned a weird buffer");
- }
- OTReleaseBuffer(readBuffer);
- }
- } while ( readBuffer != nil );
- break;
- default:
- OTDebugStr("LogNotifier: Unexpected event code");
- break;
- }
- }
-
- /////////////////////////////////////////////////////////////////////
-
- static OSStatus SetupLogging(StreamRef strm, int_t command,
- struct trace_ids traceInfo[], UInt32 traceInfoCount)
- // Sends a log configuration ioctl to the raw stream.
- // command is the ioctl, traceInfoCount is the number of
- // trace_ids in the buffer pointed to be traceInfo. Note
- // that traceInfoCount can be zero, a common case for setting
- // up an error log stream. Note that strm is assumed to be
- // in sync/blocking mode.
- //
- // Context: SystemTask /only/
- {
- OSStatus err;
- struct strioctl logIoctl;
-
- logIoctl.ic_cmd = command;
- logIoctl.ic_timout = -1;
- logIoctl.ic_dp = (char *) traceInfo;
- logIoctl.ic_len = traceInfoCount * sizeof(struct trace_ids);
-
- err = OTStreamIoctl(strm, I_STR, &logIoctl);
-
- return err;
- }
-
- /////////////////////////////////////////////////////////////////////
- // Public Entry Points
-
- // This is the raw stream which we use for logging. It is
- // set up by StartLogging and torn down by StopLogging.
-
- static StreamRef gLogStream = kOTInvalidStreamRef;
-
- extern pascal OSStatus StartLogging(Boolean logErrors,
- UInt32 traceInfoCount, struct trace_ids traceInfo[])
-
- // See comment in interface part.
- {
- OSStatus err;
- OSStatus junk;
-
- OTAssert("StartLogging: paramErr", traceInfoCount == 0 || traceInfo != nil);
-
- OTAssert("StartLogging: We're already logging!", gLogStream == kOTInvalidStreamRef);
-
- OTClearLock(&gProcessWoken);
-
- err = GetCurrentProcess(&gOurProcess);
-
- // Create and configure a stream to the log device. Note that
- // we do this stuff with the stream in sync/blocking mode, and
- // switch to async/blocking at the end.
-
- if (err == noErr) {
- gLogStream = OTStreamOpen(MI_LOG_DEVICE, 0, &err);
- }
- if (err == noErr) {
- OTStreamSetBlocking(gLogStream);
- }
- if (err == noErr && traceInfoCount > 0) {
-
- // If we've been asked to log traces, tell the log device that
- // we're the trace stream.
-
- err = SetupLogging(gLogStream, I_TRCLOG, traceInfo, traceInfoCount);
- }
- if (err == noErr && logErrors) {
-
- // If we've been asked to log error, tell the log device that
- // we're the error stream.
-
- err = SetupLogging(gLogStream, I_ERRLOG, nil, 0);
- }
- if (err == noErr) {
-
- // We want all signals to arrive in our notifier. The only one we're
- // specifically interested in is SIGPOLL (ie S_RDNORM etc) but if any
- // other signals are generated we /really/ want to know about it.
-
- err = OTStreamIoctl(gLogStream, I_SETSIG,
- (void *) (S_INPUT | S_RDNORM | S_RDBAND | S_HIPRI | S_OUTPUT | S_WRNORM |
- S_WRBAND | S_MSG | S_ERROR | S_HANGUP | S_BANDURG));
- }
- if (err == noErr) {
- OTStreamSetAsynchronous(gLogStream);
- err = OTStreamInstallNotifier(gLogStream, LogNotifier, gLogStream);
- }
-
- // Clean up.
-
- if (err != noErr) {
- junk = OTStreamClose(gLogStream);
- OTAssert("StartLogging: OTStreamClose failed", junk == noErr);
- gLogStream = kOTInvalidStreamRef;
- }
-
- return err;
- }
-
- extern pascal void StopLogging(void)
- // See comment in interface part.
- {
- OSStatus junk;
-
- OTAssert("StopLogging: We're not logging!", gLogStream != kOTInvalidStreamRef);
-
- junk = OTStreamClose(gLogStream);
- OTAssert("StopLogging: OTStreamClose failed", junk == noErr);
- gLogStream = kOTInvalidStreamRef;
- }
-
- extern pascal Boolean LoggingActive(void)
- // See comment in interface part.
- {
- return gLogStream != kOTInvalidStreamRef;
- }
-
- extern pascal UInt32 NumberOfDroppedLogEntries(void)
- // See comment in interface part.
- {
- return gDroppedLogEntries;
- }
-
- extern pascal void ForEachNewLogEntry(ProcessLogEntryProcPtr doThis, void *refCon)
- // See comment in interface part.
- {
- OTLink *thisLink;
- LogEntryPtr thisEntry;
- SInt32 junkLong;
-
- if ( gNumberInList != 0 ) {
- // OTDebugStr("ForEachNewLogEntry: Process woken with something to do");
- }
-
- OTClearLock(&gProcessWoken);
-
- // If you're having troubles understanding this, check out
- // the sample code in the section on lists in "Inside Macintosh:
- // Networking with Open Transport".
-
- do {
- thisLink = OTReverseList(OTLIFOStealList(&gNewLogEntries));
- while ( thisLink != nil ) {
- thisEntry = OTGetLinkObject(thisLink, LogEntry, fNext);
-
- doThis(thisEntry, refCon);
-
- thisLink = thisLink->fNext;
-
- ReleaseLogEntry(thisEntry);
-
- junkLong = OTAtomicAdd32(-1, (SInt32 *) &gNumberInList);
- OTAssert("ForEachNewLogEntry: ", junkLong >= 0);
- }
- } while ( gNewLogEntries.fHead != nil );
- }
-